package br.com.locasis.utilitarios;

import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;

import org.apache.commons.lang.time.DateFormatUtils;

/**
 * 
 * @author robert.resende
 * @see Classe utilitária de formatação de datas;
 * 
 */
public class DataUtil {

	private static Pattern PATTERN_HORARIO = Pattern.compile("\\d{2}\\:\\d{2}");

	/**
	 * Instancia única da classe
	 */
	private static DataUtil instance = new DataUtil();

	private DataUtil() {
	}

	public static DataUtil getInstance() {
		return instance;
	}

	/**
	 * Retorna a data atual
	 * 
	 * @param
	 * @return
	 */
	public Date getDataAtual() {
		return getCalendar().getTime();
	}

	/**
	 * Retorna a data atual formatada como dd/MM/yyyy.
	 * 
	 * @param data
	 * @return
	 */
	public String getDataFormatada(Date data) {
		String dataFormatada = null;
		SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");

		if (data != null) {
			dataFormatada = sdf.format(data);
		}

		return dataFormatada;
	}

	public String getDataFormatada(Date data, SimpleDateFormat format) {
		String dataFormatada = null;
		if (data != null) {
			dataFormatada = format.format(data);
		}
		return dataFormatada;
	}

	/**
	 * Retorna a hora formatada como HH:mm.
	 * 
	 * @param
	 * @return
	 */
	public static String getHoraFormatada(Date data) {
		String dataFormatada = null;

		if (data != null) {
			dataFormatada = new SimpleDateFormat("HH:mm").format(data);
		}

		return dataFormatada;
	}

	/**
	 * Recebe data(dd/MM/yyyy) e horario (HH:mm), retorna um Date
	 * 
	 * @param data
	 * @param horario
	 * @return
	 */
	public Date getDataComHorario(String data, String horario) {
		Calendar c = Calendar.getInstance();

		c.setTime(toDate(data));
		c.set(Calendar.HOUR_OF_DAY, getHoras(horario));
		c.set(Calendar.MINUTE, getMinutos(horario));

		return c.getTime();
	}

	public Date getDataAnterior() {
		Calendar c = Calendar.getInstance();

		c.add(Calendar.DATE, -1);

		return zeraCalendar(c.getTime());
	}

	/**
	 * Verifica se o horário é válido(horas entre 0 e 23 e minutos entre 0 e 59)
	 * 
	 * @param horarioString
	 * @return
	 */
	public boolean isHorarioValido(String horarioString) {
		boolean flag = false;

		if (isFormatacaoHorarioValida(horarioString)) {
			int hora = getHoras(horarioString);
			int minuto = getMinutos(horarioString);

			flag = (((hora >= 0) && (hora <= 23)) && ((minuto >= 0) && (minuto <= 59)));
		}

		return flag;
	}

	/**
	 * Verifica se o parametro informado refere-se a uma data valida.
	 * 
	 * @param parameterValue
	 * @return
	 */
	public boolean isDate(String parameterValue) {
		boolean isDate = false;

		try {
			new SimpleDateFormat("dd/MM/yyyy").parse(parameterValue);
			isDate = true;
		} catch (ParseException e) {
			isDate = false;
		}

		return isDate;
	}

	/**
	 * Verifica se o horário formatado é válido
	 * 
	 * @param horarioString
	 * @return
	 */
	public boolean isFormatacaoHorarioValida(String horarioString) {
		return PATTERN_HORARIO.matcher(horarioString).matches();
	}

	/**
	 * Recebe um <code>java.lang.String</code> e transforma para um Date no formato (dd/MM/YYYY)
	 * 
	 * @param dataStr
	 * @return
	 */
	public static Date toDate(String dataStr) {
		Date data = null;
		try {
			if (!UtilString.getInstance().isVazia(dataStr)) {
				SimpleDateFormat df = criarSimpleDateFormat();
				df.setLenient(false);
				data = df.parse(dataStr);
			}
		} catch (Exception e) {
			throw new IllegalArgumentException("Erro formatando data [" + dataStr + "].");
		}

		return data;
	}

	/**
	 * Recebe um Date e retorna a Descrição do mês em Português
	 * 
	 * @param data
	 * @return
	 */
	public String getMesPorExtenso(Date data) {
		return formatDate(data, "MMMM");
	}

	/**
	 * Recebe um Date e retorna a descrição do dia em Português Ex.: 'Segunda-feira'
	 * 
	 * @param data
	 * @return
	 */
	public String getDiaPorExtenso(Date data) {
		return formatDate(data, "EEEE");
	}

	/**
	 * Retorna a data por extenso em Português utilizando o padrão informado
	 * 
	 * @param data
	 * @param pattern
	 * @return
	 */
	public String formatDate(Date data, String pattern) {
		return DateFormatUtils.format(data, pattern, new Locale("pt"));
	}

	/**
	 * Adiciona dias a uma data
	 * 
	 * @param data
	 * @param dias
	 * @return
	 */
	public Date adicionarDias(Date data, int dias) {
		Calendar calendar = getCalendar();

		calendar.setTime(data);
		calendar.add(Calendar.DAY_OF_MONTH, dias);

		Date dataRetorno = calendar.getTime();

		return dataRetorno;
	}

	/**
	 * Adiciona meses a uma data
	 * 
	 * @param data
	 * @param dias
	 * @return
	 */
	public Date adicionarMeses(Date data, int meses) {
		Calendar calendar = getCalendar();

		calendar.setTime(data);
		calendar.add(Calendar.MONTH, meses);

		return calendar.getTime();
	}

	/**
	 * Adiciona anos a uma data
	 * 
	 * @param data
	 * @param dias
	 * @return
	 */
	public Date adicionarAnos(Date data, int anos) {
		Calendar calendar = getCalendar();

		calendar.setTime(data);
		calendar.add(Calendar.YEAR, anos);

		return calendar.getTime();
	}

	/**
	 * Adiciona semanas a uma data
	 * 
	 * @param data
	 * @param dias
	 * @return
	 */
	public Date adicionarSemanas(Date data, int semanas) {
		Calendar calendar = getCalendar();

		calendar.setTime(data);
		calendar.add(Calendar.WEEK_OF_MONTH, semanas);

		return calendar.getTime();
	}

	/**
	 * Verifica se a dataA é anterior à dataB sem considerar a hora;
	 * 
	 * @param dataA
	 * @param dataB
	 * @return
	 */
	public boolean isDataMenor(Date dataA, Date dataB) {
		return zeraCalendar(dataA).before(zeraCalendar(dataB));
	}

	/**
	 * Verifica se a dataA é posterior à dataB sem considerar a hora;
	 * 
	 * @param dataA
	 * @param dataB
	 * @return
	 */
	public boolean isDataMaior(Date dataA, Date dataB) {
		return zeraCalendar(dataA).after(zeraCalendar(dataB));
	}

	/**
	 * Verifica se a dataA é igual à dataB sem considerar a hora
	 * 
	 * @param dataA
	 * @param dataB
	 * @return
	 */
	public boolean isDataIgual(Date dataA, Date dataB) {
		return zeraCalendar(dataA).equals(zeraCalendar(dataB));
	}

	/**
	 * Recupera a diferença em dias das datas passadas como parâmetro
	 * 
	 * @param dataA
	 * @param dataB
	 * @return
	 */
	public static int diferencaEmDias(Date dataA, Date dataB) {
		int diferencaTemporaria = 0;
		int diferencaEmDias = 0;
		Calendar menorData = Calendar.getInstance();
		Calendar maiorData = Calendar.getInstance();
		if (dataA.compareTo(dataB) < 0) {
			menorData.setTime(dataA);
			maiorData.setTime(dataB);
		} else {
			menorData.setTime(dataB);
			maiorData.setTime(dataA);
		}
		while (menorData.get(Calendar.YEAR) != maiorData.get(Calendar.YEAR)) {
			diferencaTemporaria = 365 * (maiorData.get(Calendar.YEAR) - menorData.get(Calendar.YEAR));
			diferencaEmDias += diferencaTemporaria;
			menorData.add(Calendar.DAY_OF_YEAR, diferencaTemporaria);
		}
		if (menorData.get(Calendar.DAY_OF_YEAR) != maiorData.get(Calendar.DAY_OF_YEAR)) {
			diferencaTemporaria = maiorData.get(Calendar.DAY_OF_YEAR) - menorData.get(Calendar.DAY_OF_YEAR);
			diferencaEmDias += diferencaTemporaria;
			menorData.add(Calendar.DAY_OF_YEAR, diferencaTemporaria);
		}
		return diferencaEmDias;
	}

	/**
	 * Zera as horas e retorna somente a data.
	 * 
	 * @param data
	 * @return
	 */
	public Date zeraCalendar(Date data) {
		Calendar calendar = getCalendarInstance();
		calendar.setTime(data);
		calendar.set(Calendar.MILLISECOND, 0);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		return calendar.getTime();
	}

	/**
	 * Retorna a data com o hora de 23:59:59
	 * 
	 * @return Date
	 */

	public Date getDataHoraMaximaDia(Date data) {
		Calendar calendar = getCalendarInstance();
		calendar.setTime(data);
		calendar.set(Calendar.MILLISECOND, 23);
		calendar.set(Calendar.HOUR_OF_DAY, 23);
		calendar.set(Calendar.MINUTE, 59);
		calendar.set(Calendar.SECOND, 59);
		return calendar.getTime();
	}

	/**
	 * Recupera a data atual com a hora zerada
	 * 
	 * @return
	 */
	public Date getDataAtualHoraZero() {
		return zeraCalendar(getDataAtual());
	}

	private Calendar getCalendarInstance() {
		return Calendar.getInstance();
	}

	/**
	 * Verifica se as datas são maiores que um intervalo X em meses.
	 * 
	 * @param dataInicio
	 * @param dataFim
	 * @param intervalo
	 * @return
	 */
	public boolean isIntervaloMaior(Date dataInicio, Date dataFim, int intervalo) {
		Date dataComp = adicionarMeses(dataInicio, intervalo);

		return dataComp.before(dataFim);
	}

	/**
	 * Retorna o ano
	 * 
	 * @param data
	 * @return
	 */
	public int getAno(Date data) {
		Calendar calendarioData = Calendar.getInstance();
		calendarioData.setTime(data);
		return calendarioData.get(Calendar.YEAR);
	}

	/**
	 * Retorna o Dia do Mês
	 * 
	 * @param data
	 * @return
	 */
	public String getDia(Date data) {
		Calendar calendarioData = Calendar.getInstance();
		calendarioData.setTime(data);

		return String.valueOf(calendarioData.get(Calendar.DAY_OF_MONTH));
	}

	/**
	 * Retorna o Mês
	 * 
	 * @param data
	 * @return
	 */
	public String getMes(Date data) {
		Calendar calendarioData = Calendar.getInstance();
		calendarioData.setTime(data);

		return String.valueOf(calendarioData.get(Calendar.MONTH));
	}

	private int getMinutos(String horarioString) {
		return Integer.parseInt(horarioString.substring(3, horarioString.length()));
	}

	private int getHoras(String horarioString) {
		return Integer.parseInt(horarioString.substring(0, 2));
	}

	private static SimpleDateFormat criarSimpleDateFormat() {
		return new SimpleDateFormat("dd/MM/yyyy");
	}

	private Calendar getCalendar() {
		return Calendar.getInstance();
	}

	/**
	 * @param date
	 * @return
	 */
	static public String BrasilianDate2String(Date date) {
		DateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy");
		String result = simpleDateFormat.format(date);
		return result;
	}

	/**
	 * @param date
	 * @return
	 */
	static public String BrasilianDateTime2String(Date date) {
		DateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
		String result = simpleDateFormat.format(date);
		return result;
	}

	/**
	 * @param string
	 * @return
	 */
	static public Date String2BrasilianDate(String string) {
		Date result = new Date();
		if (!string.equals("")) {
			if (!string.equals("31.12.9999")) {
				DateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy");
				try {
					result = simpleDateFormat.parse(string);
				} catch (ParseException e) {
					e.printStackTrace();
				}
			} else {
				result = null;
			}
		}
		return result;
	}

	/**
	 * @param string
	 * @return
	 */
	static public Date String2BrasilianDateTraco(String string) {
		Date result = new Date();
		if (!string.equals("")) {
			if (!string.equals("9999-12-31")) {
				DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
				try {
					result = simpleDateFormat.parse(string);
				} catch (ParseException e) {
					e.printStackTrace();
				}
			} else {
				result = null;
			}
		}
		return result;
	}

	static public Timestamp stringToTimestamp(String string) {
		DateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH.mm.ss.SSSSSS");
		Date result = null;
		try {
			result = simpleDateFormat.parse(string);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		Timestamp ts = new Timestamp(result.getTime());
		return ts;
	}

	/**
	 * 
	 * @param dt
	 * @return
	 */
	static public Date formatDate(String dt) {

		SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");

		Date data = null;
		try {
			data = sdf.parse(dt);

		} catch (ParseException e) {

			e.printStackTrace();
		}

		return data;
	}

	/**
	 * Recupera lista de Dates contendo os anos anteriores ao ano atual, de acordo com o parametro passado.
	 * 
	 * @param numberYears
	 * @return List<Date>
	 */
	static public List<Date> getBeforeYears(int numberYears) {

		List<Date> dates = new ArrayList<Date>();
		GregorianCalendar gc = new GregorianCalendar();
		dates.add(gc.getTime());

		for (int i = 0; i < numberYears; i++) {
			gc.add(GregorianCalendar.YEAR, -1);
			dates.add(gc.getTime());
		}
		return dates;
	}

	/**
	 * Recupera o ano de um Date.
	 * 
	 * @param date
	 * @return String
	 */
	static public String getYear(Date date) {
		SimpleDateFormat format = new SimpleDateFormat("yyyy");
		String strYear = format.format(date);
		return strYear;
	}
}
